﻿using System;
using GeoAPI.Geometries;
using GeoAPI.Operation.Buffer;
using YJKGridTopologyCal.Geometries;
using YJKGridTopologyCal.Mathematics;
using YJKGridTopologyCal.Noding;
using YJKGridTopologyCal.Noding.Snapround;

namespace YJKGridTopologyCal.Operation.Buffer
{
	public class BufferOp
	{
		private static double PrecisionScaleFactor(IGeometry g, double distance, int maxPrecisionDigits)
		{
			Envelope envelopeInternal = g.EnvelopeInternal;
			double num = MathUtil.Max(Math.Abs(envelopeInternal.MaxX), Math.Abs(envelopeInternal.MaxY), Math.Abs(envelopeInternal.MinX), Math.Abs(envelopeInternal.MinY));
			double num2 = (distance > 0.0) ? distance : 0.0;
			int num3 = (int)(Math.Log(num + 2.0 * num2) / Math.Log(10.0) + 1.0);
			int num4 = maxPrecisionDigits - num3;
			return Math.Pow(10.0, (double)num4);
		}

		public static IGeometry Buffer(IGeometry g, double distance)
		{
			return new BufferOp(g).GetResultGeometry(distance);
		}

		public static IGeometry Buffer(IGeometry g, double distance, IBufferParameters parameters)
		{
			return new BufferOp(g, parameters).GetResultGeometry(distance);
		}

		public static IGeometry Buffer(IGeometry g, double distance, int quadrantSegments)
		{
			return new BufferOp(g)
			{
				QuadrantSegments = quadrantSegments
			}.GetResultGeometry(distance);
		}

		[Obsolete("use Buffer(IGeometry, distance, IBufferParameters) instead!")]
		public static IGeometry Buffer(IGeometry g, double distance, int quadrantSegments, BufferStyle endCapStyle)
		{
			return new BufferOp(g)
			{
				QuadrantSegments = quadrantSegments,
				BufferStyle = endCapStyle
			}.GetResultGeometry(distance);
		}

		public BufferOp(IGeometry g)
		{
			this._argGeom = g;
		}

		public BufferOp(IGeometry g, IBufferParameters bufParams)
		{
			this._argGeom = g;
			this._bufParams = bufParams;
		}

		[Obsolete]
		public BufferStyle BufferStyle
		{
			get
			{
				return (BufferStyle)this._bufParams.EndCapStyle;
			}
			set
			{
				this._bufParams.EndCapStyle = (EndCapStyle)value;
			}
		}

		public int QuadrantSegments
		{
			get
			{
				return this._bufParams.QuadrantSegments;
			}
			set
			{
				this._bufParams.QuadrantSegments = value;
			}
		}

		public IGeometry GetResultGeometry(double distance)
		{
			this._distance = distance;
			this.ComputeGeometry();
			return this._resultGeometry;
		}

		private void ComputeGeometry()
		{
			this.BufferOriginalPrecision();
			if (this._resultGeometry != null)
			{
				return;
			}
			IPrecisionModel precisionModel = this._argGeom.Factory.PrecisionModel;
			if (precisionModel.PrecisionModelType == PrecisionModels.Fixed)
			{
				this.BufferFixedPrecision(precisionModel);
				return;
			}
			this.BufferReducedPrecision();
		}

		private void BufferReducedPrecision()
		{
			for (int i = 12; i >= 0; i--)
			{
				try
				{
					this.BufferReducedPrecision(i);
				}
				catch (TopologyException saveException)
				{
					this._saveException = saveException;
				}
				if (this._resultGeometry != null)
				{
					return;
				}
			}
			throw this._saveException;
		}

		private void BufferOriginalPrecision()
		{
			try
			{
				BufferBuilder bufferBuilder = new BufferBuilder(this._bufParams);
				this._resultGeometry = bufferBuilder.Buffer(this._argGeom, this._distance);
			}
			catch (Exception saveException)
			{
				this._saveException = saveException;
			}
		}

		private void BufferReducedPrecision(int precisionDigits)
		{
			PrecisionModel fixedPrecModel = new PrecisionModel(BufferOp.PrecisionScaleFactor(this._argGeom, this._distance, precisionDigits));
			this.BufferFixedPrecision(fixedPrecModel);
		}

		private void BufferFixedPrecision(IPrecisionModel fixedPrecModel)
		{
			INoder noder = new ScaledNoder(new MCIndexSnapRounder(new PrecisionModel(1.0)), fixedPrecModel.Scale);
			this._resultGeometry = new BufferBuilder(this._bufParams)
			{
				WorkingPrecisionModel = fixedPrecModel,
				Noder = noder
			}.Buffer(this._argGeom, this._distance);
		}

		private const int MaxPrecisionDigits = 12;

		private readonly IGeometry _argGeom;

		private double _distance;

		private readonly IBufferParameters _bufParams = new BufferParameters();

		private IGeometry _resultGeometry;

		private Exception _saveException;
	}
}
